package cn.yuitstyle.fss.minio.starter.util;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.yuitstyle.fss.minio.starter.exception.MinioFileException;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author yuwg
 */
@Component
@Slf4j
public class MinioFileService {

    @Autowired
    private MinioClient minioClient;

    private static String prefix = "test/";

    /**
     * 判断文件是否存在
     *
     * @param bucketName bucketName
     * @param objectName objectName
     * @return 存在true 不存在 false
     */
    public boolean isObjectExists(String bucketName, String objectName) {
        boolean exist = true;
        try {
            minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 判断文件是否存在，异常：", e);
            throw new MinioFileException("<判断文件是否存在>异常");
        }
        return exist;
    }

    /**
     * 判断文件夹是否存在
     *
     * @param bucketName bucketName
     * @param folderName 文件夹路径
     * @return 存在 true 不存在 false
     */
    public boolean isFolderExists(String bucketName, String folderName) {
        boolean exist = false;
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .prefix(folderName)
                    .recursive(false)
                    .build());
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir() && folderName.equals(item.objectName())) {
                    exist = true;
                }
            }
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 判断文件夹是否存在，异常：", e);
            throw new MinioFileException("<判断文件夹是否存在>异常");
        }
        return exist;
    }

    /**
     * 根据前缀获取文件
     *
     * @param bucketName bucketName
     * @param prefix     前缀
     * @param recursive  是否递归查询
     * @return MinioItem 列表
     */
    @SneakyThrows(Exception.class)
    public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, Boolean recursive) {
        List<Item> items = new ArrayList<>();
        try {

            Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                    .prefix(prefix)
                    .recursive(recursive)
                    .build());
            if (results.iterator().hasNext()) {
                for (Result<Item> result : results) {
                    items.add(result.get());
                }
            }
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 根据前缀获取文件，异常：", e);
            throw new MinioFileException("<根据前缀获取文件>异常");
        }
        return items;
    }

    /**
     * 获取文件流
     *
     * @param bucketName bucketName
     * @param objectName 文件名
     * @return 二进制流
     */
    public InputStream getObject(String bucketName, String objectName) {
        try {

            return minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 获取文件，异常：", e);
            throw new MinioFileException("<获取文件>异常");
        }
    }

    /**
     * 断点下载
     *
     * @param bucketName bucketName
     * @param objectName 文件名
     * @param offset     起始字节的位置
     * @param length     长度
     * @return 二进制流
     */
    public InputStream getObject(String bucketName, String objectName, long offset, long length) {
        if (!isObjectExists(bucketName, objectName)) {
            throw new MinioFileException("文件不存在");
        }
        try {
            return minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .offset(offset)
                    .length(length)
                    .build());
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 断点下载，异常：", e);
            throw new MinioFileException("<断点下载>异常");
        }


    }

    /**
     * 获取路径下的文件列表
     *
     * @param bucketName bucketName
     * @param prefix     前缀
     * @param recursive  是否递归
     * @return 二进制文件流
     */
    public Iterable<Result<Item>> listObjects(String bucketName, String prefix, Boolean recursive) {
        try {

            return minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(prefix)
                            .recursive(recursive)
                            .build()
            );
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 获取路径下的文件列表，异常", e);
            throw new MinioFileException("<获取路径下的文件列表>异常");
        }
    }

    /**
     * 使用MultipartFile进行文件上传
     *
     * @param bucketName  bucketName
     * @param file        文件
     * @param objectName  文件名称
     * @param contentType 类型
     */
    public ObjectWriteResponse uploadMultipartFile(String bucketName, MultipartFile file, String objectName, String contentType) {
        try {
            InputStream inputStream = file.getInputStream();
            ObjectWriteResponse objectWriteResponse = minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(prefix+objectName)
                            .contentType(contentType)
                            .stream(inputStream, inputStream.available(), -1)
                            .build()
            );
            inputStream.close();
            return objectWriteResponse;
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 使用MultipartFile进行文件上传，异常：", e);
            throw new MinioFileException("<使用MultipartFile进行文件上传>异常");
        }
    }

    /**
     * 图片上传
     *
     * @param bucketName  bucketName
     * @param imageBase64 图片base64
     * @param imageName   图片名称
     */
    public ObjectWriteResponse uploadImageFile(String bucketName, String imageBase64, String imageName) {
        try {
            if (StrUtil.isNotBlank(imageBase64)) {
                InputStream inputStream = base64ToInputStream(imageBase64);
                String fileName = imageName + ".jpg";
                String yyyyMMddHH = DateUtil.format(LocalDateTime.now(), "yyyyMMddHH");
                fileName = yyyyMMddHH + "/" + fileName;

                ObjectWriteResponse objectWriteResponse = uploadStreamFile(bucketName, fileName, inputStream);
                inputStream.close();
                return objectWriteResponse;
            }
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 图片上传，异常：", e);
            throw new MinioFileException("图片上传异常");
        }
        return null;
    }

    public static InputStream base64ToInputStream(String base64) {
        ByteArrayInputStream byteArrayInputStream = null;
        try {
            byte[] bytes = Base64Decoder.decode(base64);
            byteArrayInputStream = new ByteArrayInputStream(bytes);
        } catch (Exception e) {
            log.error("base64转文件流异常", e);
            throw new MinioFileException("base64转文件流异常");
        }
        return byteArrayInputStream;
    }

    /**
     * 上传本地文件
     *
     * @param bucketName bucketName
     * @param objectName 对象名称
     * @param filePath   本地文件路径
     */
    public ObjectWriteResponse uploadLocalFile(String bucketName, String objectName, String filePath) {
        try {
            return minioClient.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .filename(filePath)
                            .build()
            );
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 上传本地文件，异常：", e);
            throw new MinioFileException("<上传本地文件>异常");
        }
    }

    /**
     * 上传流文件
     *
     * @param bucketName  bucketName
     * @param objectName  文件对象
     * @param inputStream 流程流
     */
    public ObjectWriteResponse uploadStreamFile(String bucketName, String objectName, InputStream inputStream) {
        try {
            return minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(inputStream, inputStream.available(), -1)
                            .build()
            );
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 上传流文件，异常", e);
            throw new MinioFileException("<上传流文件>异常");
        }
    }

    /**
     * 创建目录
     *
     * @param bucketName bucketName
     * @param dirName    目录路径
     */
    public ObjectWriteResponse creatDir(String bucketName, String dirName) {
        try {
            return minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(dirName)
                            .stream(new ByteArrayInputStream(new byte[]{}), 0, -1)
                            .build()
            );
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 创建目录，异常：", e);
            throw new MinioFileException("<创建目录>异常");
        }
    }

    /**
     * 获取文件信息
     *
     * @param bucketName bucketName
     * @param objectName objectName
     */
    public String getFileStatusInfo(String bucketName, String objectName) {
        try {
            return minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            ).toString();
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 获取文件信息，异常：", e);
            throw new MinioFileException("<获取文件>信息异常");
        }
    }

    /**
     * 拷贝文件
     *
     * @param bucketName       bucketName
     * @param objectName       objectName
     * @param targetBucketName 目标bucketName
     * @param targetObjectName 目标objectName
     * @return
     */
    public ObjectWriteResponse copyFile(String bucketName, String objectName, String targetBucketName, String targetObjectName) {
        if (!isObjectExists(bucketName, objectName)) {
            throw new MinioFileException("文件不存在");
        }
        try {
            return minioClient.copyObject(
                    CopyObjectArgs.builder()
                            .source(CopySource.builder()
                                    .bucket(bucketName)
                                    .object(objectName)
                                    .build())
                            .bucket(targetBucketName)
                            .object(targetObjectName)
                            .build()
            );
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 拷贝文件，异常：", e);
            throw new MinioFileException("<拷贝文件>异常");
        }
    }

    /**
     * 删除文件
     *
     * @param bucketName bucketName
     * @param objectName objectName
     */
    public void removeFile(String bucketName, String objectName) {
        if (!isObjectExists(bucketName, objectName)) {
            throw new MinioFileException("文件不存在");
        }
        try {
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            );
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 删除文件，异常：", e);
            throw new MinioFileException("<删除文件>异常");
        }
    }

    /**
     * 批量删除文件
     *
     * @param bucketName bucketName
     * @param keys       objectName 列表
     */
    public void batchRemoveFile(String bucketName, List<String> keys) {
        try {
            minioClient.removeObjects(
                    RemoveObjectsArgs.builder()
                            .bucket(bucketName)
                            .objects(keys.stream().map(DeleteObject::new).collect(Collectors.toList()))
                            .build()
            );
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 批量删除文件，异常：", e);
            throw new MinioFileException("<批量删除文件>异常");
        }
    }

    /**
     * 获取文件外链
     *
     * @param bucketName bucketName
     * @param objectName objectName
     * @param expires    过期时间 <=7 秒
     * @return url
     */
    public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) {
        if (!isObjectExists(bucketName, objectName)) {
            throw new MinioFileException("文件不存在");
        }
        try {

            return minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .expiry(expires)
                            .bucket(bucketName)
                            .object(objectName)
                            .build()

            );
        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 获取文件外链，异常：", e);
            throw new MinioFileException("<获取文件外链>异常");
        }
    }

    /**
     * 获取文件外链
     *
     * @param bucketName bucketName
     * @param objectName objectName
     * @return url
     */
    public String getPresignedObjectUrl(String bucketName, String objectName) {
        if (!isObjectExists(bucketName, objectName)) {
            throw new MinioFileException("文件不存在");
        }
        try {
            return minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .method(Method.GET)
                            .build()
            );


        } catch (Exception e) {
            log.error("[minio文件工具类]>>> 获取文件外链，异常：", e);
            throw new MinioFileException("<获取文件外链>异常");
        }
    }


    @SneakyThrows(UnsupportedEncodingException.class)
    public String getUtf8ByURLDecoder(String str) {
        String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
        return URLDecoder.decode(url, "UTF-8");
    }

}
